package edu.pku.sei.bridge.mdabase.adapter;

import java.io.File;
import java.util.HashMap;
import java.util.Iterator;

import org.eclipse.emf.common.util.EList;
import org.eclipse.emf.common.util.URI;
import org.eclipse.emf.ecore.EAttribute;
import org.eclipse.emf.ecore.EClass;
import org.eclipse.emf.ecore.EClassifier;
import org.eclipse.emf.ecore.EFactory;
import org.eclipse.emf.ecore.EObject;
import org.eclipse.emf.ecore.EPackage;
import org.eclipse.emf.ecore.EReference;
import org.eclipse.emf.ecore.impl.EFactoryImpl;
import org.eclipse.emf.ecore.resource.Resource;
import org.eclipse.emf.ecore.resource.ResourceSet;
import org.eclipse.emf.ecore.resource.impl.ResourceSetImpl;
import org.eclipse.emf.ecore.xmi.impl.EcoreResourceFactoryImpl;

import edu.pku.sei.bridge.common.Utility;
import edu.pku.sei.mdabase.basictypes.PrimitiveType;
import edu.pku.sei.mdabase.core.IDataType;
import edu.pku.sei.mdabase.infrastructure.metamodel.MetaClass;
import edu.pku.sei.mdabase.infrastructure.metamodel.MetaModelGraph;
import edu.pku.sei.mdabase.infrastructure.metamodel.MetaReference;











public class ECoreLoader {
	private HashMap<EClass,MetaClass> classMap = new HashMap<EClass,MetaClass>();
	private HashMap<EReference,MetaReference> referenceMap = new HashMap<EReference,MetaReference>();
	
	
	
	
	private static HashMap<String, ECoreLoader> TypeNameToLoader = new HashMap<String, ECoreLoader>();
	
	private static HashMap<MetaModelGraph, ECoreLoader> RTypeGraphToLoader = new HashMap<MetaModelGraph, ECoreLoader>();

	public static ECoreLoader getECoreLoader(String typeName){
		ECoreLoader el = TypeNameToLoader.get(typeName);
		return el;
	}
	
	public static ECoreLoader getECoreLoader(String typeName, String fileName){
		File f = new File(fileName);
		
		return getECoreLoader(typeName,f);
	}
	
	public static ECoreLoader getECoreLoader(String typeName, File afile){
		if (afile.exists()){
			ECoreLoader el = TypeNameToLoader.get(typeName);
			if(el != null)
				return el;
			else {
				el = new ECoreLoader(typeName, afile);
				TypeNameToLoader.put(typeName, el);
				return el;
			}
		}else{
			System.out.println(afile.getName() + " dose not exist");
			return null;
		}
	}
	
	private File file = null;
	
	private EPackage pack = null;
	
	private MetaModelGraph metamodel = null;
	
	private ECoreLoader(String typeName, File afile) {
		file = afile;
	}

	public File getFile() {
		return file;
	}
	
	public EPackage getRootPackage(){
		if(pack != null)
			return pack;
		ResourceSet resourceSet = Utility.resourceSet;
		
		resourceSet.getResourceFactoryRegistry().getExtensionToFactoryMap().put(Resource.Factory.Registry.DEFAULT_EXTENSION, new EcoreResourceFactoryImpl());
		URI fileURI = URI.createFileURI(file.getAbsolutePath());
		Resource resource = resourceSet.getResource(fileURI, true);
		EList<EObject> list = resource.getContents();
		pack = (EPackage) list.get(0);
		
		if(list.size()>1)
			System.out.println("more elements in list");
		
		return pack;
	}
	
	public MetaModelGraph load() {
		if(metamodel != null)
			return metamodel;
		if(pack == null)
			pack = getRootPackage();
		
		metamodel = new MetaModelGraph();
		metamodel.setName(pack.getName());
		
		
		loadPackage(pack,pack.getEFactoryInstance());
		
		return metamodel;
	}
	
	public void loadPackage(EPackage pack, EFactory factory){
		EList<EClassifier> classList = pack.getEClassifiers();
		Iterator<EClassifier> iter = classList.iterator();
		while(iter.hasNext()){
			EClassifier ec = iter.next();
			if(ec instanceof EClass)
				loadClassifier((EClass) ec,factory);
			else if(ec instanceof EPackage)
				loadPackage((EPackage)ec,factory);
		}
	}

	private MetaClass loadClassifier(EClass eclass, EFactory factory) {
		if (eclass == null)
			return null;
		MetaClass cls = classMap.get(eclass);
		if(cls != null)
			return cls;
		
		cls = new MetaClass();
		
		cls.setName(eclass.getName());
		
		classMap.put(eclass, cls);
		
		EList<EClass> superTypes = eclass.getESuperTypes();
		
		if (!superTypes.isEmpty()) {
			
			Iterator<EClass> iter = superTypes.iterator();
			while (iter.hasNext()) {
				EClass c = iter.next();
				cls.addSuperType(loadClassifier(c,factory));
			}
			
		}
		EList<EAttribute> attrs = eclass.getEAttributes();
		
		if(!attrs.isEmpty()){
			Iterator<EAttribute> attrIter = attrs.iterator();
			while(attrIter.hasNext()){
				EAttribute attr = attrIter.next();
				IDataType type = PrimitiveType.TYPE_STRING;
				
				String typeName = attr.getEAttributeType().getName();
				if(typeName.equals("EString"))
					type = PrimitiveType.TYPE_STRING;
				else if(typeName.equals("EBoolean"))
					type = PrimitiveType.TYPE_BOOLEAN;
				else if(typeName.equals("EInt"))
					type = PrimitiveType.TYPE_INTEGER;
				
				Object def = null;
				
				if(factory!=null&&attr.getDefaultValueLiteral()!=null){
					try {
						def = attr.getEAttributeType().getEPackage().getEFactoryInstance().convertToString(attr.getEAttributeType(), attr.getDefaultValueLiteral());
					} catch (Exception e) {
						e.printStackTrace();
					}
				}
				
				cls.addAttribute(attr.getName(), type, def);
			}
		}
		EList<EReference> refs = eclass.getEReferences();
		if(!refs.isEmpty()){
			Iterator<EReference> refIter = refs.iterator();
			while(refIter.hasNext()){
				EReference ref = refIter.next();
				loadReference(ref,factory);
			}
		}
		metamodel.addNode(cls);
		return cls;
	}
	
	private MetaReference loadReference(EReference eref, EFactory factory) {
		if(eref == null)
			return null;
		
		MetaReference ref = referenceMap.get(eref);
		if(ref!=null)
			return ref;
		
		ref = new MetaReference();
		referenceMap.put(eref, ref);

		ref.setName(eref.getName());
		ref.setComposite(eref.isContainment());
		ref.setMultiple(eref.getUpperBound()!=1);

		ref.setSource(loadClassifier(eref.getEContainingClass(),factory));
		ref.setTarget(loadClassifier(eref.getEReferenceType(),factory));
		

		EReference opposite = eref.getEOpposite();
		ref.setOpposite(loadReference(opposite,factory));		
		
		metamodel.addEdge(ref);
		
		return ref;
	}

	public MetaReference getMetaReference(EReference eref){
		return referenceMap.get(eref);
	}
	
	public MetaClass getMetaClass(EClass ecls){
		return classMap.get(ecls);
	}
}
